package com.anjiplus.template.gaea.business.modules.flowable.process.service.impl;

import com.anji.plus.gaea.cache.CacheHelper;
import com.anji.plus.gaea.constant.GaeaConstant;
import com.anji.plus.gaea.curd.mapper.GaeaBaseMapper;
import com.anji.plus.gaea.exception.BusinessExceptionBuilder;
import com.anji.plus.gaea.holder.UserContentHolder;
import com.anji.plus.gaea.utils.GaeaBeanUtils;
import com.anji.plus.gaea.utils.GaeaUtils;
import com.anjiplus.template.gaea.business.modules.flowable.process.controller.dto.CommentDTO;
import com.anjiplus.template.gaea.business.modules.flowable.process.controller.dto.TaskInfoDTO;
import com.anjiplus.template.gaea.business.modules.flowable.process.controller.param.ActReProcdefParam;
import com.anjiplus.template.gaea.business.modules.flowable.process.controller.param.RunningTaskParam;
import com.anjiplus.template.gaea.business.modules.flowable.process.dao.ActReProcdefMapper;
import com.anjiplus.template.gaea.business.modules.flowable.process.dao.entity.ActReProcdef;
import com.anjiplus.template.gaea.business.modules.flowable.process.service.ProcessService;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.task.Comment;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl;
import org.flowable.ui.modeler.domain.Model;
import org.flowable.ui.modeler.repository.ModelRepository;
import org.flowable.ui.modeler.serviceapi.ModelService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 流程相关服务
 *
 * @author lirui
 * @since 2021-03-18 14:41:29
 */
@Service
public class ProcessServiceImpl implements ProcessService {

    /**
     * 任务编号在redis中的编号
     */
    private final static String FLOWABLE_TASK_KEY = "flowable:task:no";

    /**
     * 日期格式
     */
    private final static String DATE_PATTERN = "yyyyMMddHHmmssSSS";

    /**
     * 流程编号
     */
    private final static String PROCESS_NO = "processNo";


    /**
     * 主题
     */
    private final static String TITLE = "title";

    @Autowired
    private ActReProcdefMapper  actReProcdefMapper;

    @Autowired
    private HistoryService historyService;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private CacheHelper cacheHelper;

    @Autowired
    private TaskService taskService;

    @Autowired
    private ModelService modelService;

    @Autowired
    private ModelRepository modelRepository;

    @Override
    public GaeaBaseMapper<ActReProcdef> getMapper() {
        return  actReProcdefMapper;
    }

    @Override
    public IPage<ActReProcdef> page(ActReProcdefParam pageParam, Wrapper<ActReProcdef> wrapper) {

        List<ProcessDefinition> processDefinitions = repositoryService.createProcessDefinitionQuery()
                .latestVersion().listPage(pageParam.getOffset(), pageParam.getPageSize());

        int size = repositoryService.createProcessDefinitionQuery()
                .latestVersion().list().size();

        IPage<ActReProcdef> page = new Page<>();

        List<ActReProcdef> reProcdefDTOS = processDefinitions.stream().map(processDefinition -> {
            ActReProcdef dto = new ActReProcdef();
            BeanUtils.copyProperties(processDefinition, dto);
            return dto;
        }).collect(Collectors.toList());

        page.setRecords(reProcdefDTOS);
        page.setCurrent(pageParam.getPageSize());
        page.setPages(pageParam.getPageSize());
        page.setTotal(size);
        return page;
    }


    @Override
    public IPage<TaskInfoDTO> getRunningTasks(RunningTaskParam param) {
        String username = UserContentHolder.getContext().getUsername();
        TaskQuery taskQuery = taskService.createTaskQuery();

        //任务编号查询
        if (StringUtils.isNotBlank(param.getProcessNo())) {
            taskQuery.processVariableValueEquals(PROCESS_NO, param.getProcessNo());
        }

        //主题
        if(StringUtils.isNotBlank(param.getTitle())) {
            taskQuery.processVariableValueLike(TITLE, GaeaConstant.PERCENT_SIGN + param.getTitle() + GaeaConstant.PERCENT_SIGN);
        }
        taskQuery.taskCandidateOrAssigned(username).orderByTaskCreateTime().desc().includeProcessVariables().includeTaskLocalVariables();

        long count = taskQuery.count();

        List<TaskInfoDTO> result = taskQuery.listPage(param.getOffset(), param.getPageSize()).stream()
                .filter(task -> task instanceof TaskEntityImpl)
                .map(task -> {

                    TaskInfoDTO taskInfoDTO = new TaskInfoDTO();
                    GaeaBeanUtils.copyAndFormatter(task, taskInfoDTO);
                    return taskInfoDTO;
                }).collect(Collectors.toList());

        return getIPage(param, count, result);
    }

    @Override
    public IPage<TaskInfoDTO> getHistoricTaskInstanceIPage(RunningTaskParam param) {
        String username = UserContentHolder.getContext().getUsername();
        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery()
                .involvedUser(username);
        //任务编号查询
        if (StringUtils.isNotBlank(param.getProcessNo())) {
            historicProcessInstanceQuery.variableValueEquals(PROCESS_NO, param.getProcessNo());
        }

        //主题
        if(StringUtils.isNotBlank(param.getTitle())) {
            historicProcessInstanceQuery.variableValueLike(TITLE, GaeaConstant.PERCENT_SIGN + param.getTitle() + GaeaConstant.PERCENT_SIGN);
        }
        historicProcessInstanceQuery.orderByProcessInstanceEndTime().desc()
                .includeProcessVariables();

        long count = historicProcessInstanceQuery.count();

        List<HistoricProcessInstance> historicProcessInstances = historicProcessInstanceQuery.listPage(param.getOffset(), param.getPageSize());


        List<TaskInfoDTO> result = historicProcessInstances.stream().map(historicProcessInstance -> {
            TaskInfoDTO taskInfoDTO = new TaskInfoDTO();
            GaeaBeanUtils.copyAndFormatter(historicProcessInstance, taskInfoDTO);
            return taskInfoDTO;
        }).collect(Collectors.toList());

        return getIPage(param, count, result);
    }


    @Override
    public TaskInfoDTO taskDetail(String taskId) {

        Task task = taskService.createTaskQuery().taskId(taskId).includeProcessVariables().includeTaskLocalVariables().singleResult();

        TaskInfoDTO taskInfoDTO = new TaskInfoDTO();
        BeanUtils.copyProperties(task, taskInfoDTO);
        List<Comment> processInstanceComments = taskService.getProcessInstanceComments(task.getProcessInstanceId());

        List<CommentDTO> commentDTOS = processInstanceComments.stream().map(comment -> {
            CommentDTO commentDTO = new CommentDTO();
            GaeaBeanUtils.copyAndFormatter(comment, commentDTO);
            return commentDTO;
        }).collect(Collectors.toList());
        taskInfoDTO.setComments(commentDTOS);
        return taskInfoDTO;
    }

    @Override
    public TaskInfoDTO processInstanceDetail(String processInstanceId) {

        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId)
                .includeProcessVariables()
                .singleResult();
        TaskInfoDTO taskInfoDTO = new TaskInfoDTO();
        BeanUtils.copyProperties(historicProcessInstance, taskInfoDTO);
        List<Comment> processInstanceComments = taskService.getProcessInstanceComments(processInstanceId);

        List<CommentDTO> commentDTOS = processInstanceComments.stream().map(comment -> {
            CommentDTO commentDTO = new CommentDTO();
            GaeaBeanUtils.copyAndFormatter(comment, commentDTO);
            return commentDTO;
        }).collect(Collectors.toList());
        taskInfoDTO.setComments(commentDTOS);
        return taskInfoDTO;
    }

    @Override
    public void startProcess(String processDefinitionId, String businessKey, Map<String,Object> map) {

        //生成唯一任务编号
        String processNo = GaeaUtils.formatDate(new Date(), DATE_PATTERN) + cacheHelper.increment(FLOWABLE_TASK_KEY);

        if(CollectionUtils.isEmpty(map)) {
            map = new HashMap<>(2);
        }

        map.put(PROCESS_NO, processNo);

        //设置发起人
        Authentication.setAuthenticatedUserId(UserContentHolder.getContext().getUsername());
        runtimeService.startProcessInstanceById(processDefinitionId, businessKey, map);
    }

    @Override
    public void completeTask(String taskId, Map<String, Object> requestBody) {
        String processInstanceId = taskService.createTaskQuery().taskId(taskId).singleResult().getProcessInstanceId();

        //添加当前处理的用户
        Authentication.setAuthenticatedUserId(UserContentHolder.getContext().getUsername());
        //添加备注
        taskService.addComment(taskId, processInstanceId, requestBody.get("comment").toString());

        //完成任务
        taskService.complete(taskId, requestBody);
    }

    @Override
    public Model getFormModel(String processDefinitionId) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(processDefinitionId).latestVersion().singleResult();

        Model modelRes = modelRepository.findByKeyAndType(processDefinition.getKey(), 0).get(0);
        Model model = modelService.getModel(modelRes.getId());

        byte[] bytes = modelService.getBpmnXML(model);
        if (bytes == null) {
            throw BusinessExceptionBuilder.build("");
        }

        BpmnModel bpmnModel = modelService.getBpmnModel(model);

        if (bpmnModel.getProcesses().size() == 0) {
            throw BusinessExceptionBuilder.build("");
        }

        Process process = bpmnModel.getMainProcess();
        Collection<FlowElement> flowElements = process.getFlowElements();

        String formKey = null;
        for (FlowElement flowElement : flowElements) {

            if (flowElement instanceof StartEvent) {
                //位于开始节点上表单元素
                StartEvent startEvent = (StartEvent) flowElement;
                if (startEvent.getFormKey() != null && startEvent.getFormKey().length() > 0) {
                    formKey = startEvent.getFormKey();
                }
            } else if (flowElement instanceof UserTask) {
                //位于任务节点上表单元素
                UserTask userTask = (UserTask) flowElement;
                if (userTask.getFormKey() != null && userTask.getFormKey().length() > 0) {
                    formKey = userTask.getFormKey();
                }
            }
        }
        return modelRepository.findByKeyAndType(formKey, 2).get(0);
    }


    private <T> IPage<T> getIPage(RunningTaskParam param, long count, List<T> list) {
        IPage<T> taskIPage = new Page<>();
        taskIPage.setRecords(list);
        taskIPage.setTotal(count);
        taskIPage.setCurrent(param.getPageNumber());

        taskIPage.setPages(param.getPageSize());
        return taskIPage;
    }
}
